package com.za.plugin;


import cn.hutool.core.annotation.AnnotationUtil;
import com.za.plugin.annotation.SkipTransToDM;
import com.za.plugin.pojo.SqlAndParam;
import com.za.plugin.transfer.form.insertupdate.*;
import com.za.plugin.util.DataSourceUtil;
import com.za.plugin.util.SqlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Component
@Slf4j
public class TransInterceptor implements Interceptor {

    @Resource
    private DataSourceUtil dataSourceUtil;

    private Map<String, ArrayList<String>> interceptMethodMap;

    PointStyleTransfer pointStyleTransfer = new PointStyleTransfer();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];

//      id 不注释是因为便于打断点调试
        String id = ms.getId();
        initInterceptMethodMap();
        // 下面的代码可以使用 TransToDM 注解跳过执行这个插件
        if (skipPlugin(id, id.lastIndexOf("."))) {
            return invocation.proceed();
        } /*else if (isNeedInit()) {
            initInterceptMethodMap();
        }*/

        // mapper 接口方法是单参数且是一个普通对象，那么参数用 parameterObj 对象，否则用 parameterMap 代表参数

        Object parameterObj = args[1];
        SqlSource sqlSource = ms.getSqlSource();
        BoundSql boundSql = getBoundSql(args, sqlSource, parameterObj);

        String sql = boundSql.getSql();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
       // log.info("TransInterceptor=====原始sql===={}",sql);
       // log.info("TransInterceptor=====参数名称===={}",JSONUtil.toJsonStr(parameterMappings));
       // log.info("TransInterceptor=====实际参数值===={}",JSONUtil.toJsonStr(parameterObj));

        List<ParameterMapping> parameterMappingsCopy = new ArrayList<>(parameterMappings);
        parameterMappings = new ArrayList<>();
        boolean isModify = false;



        sql = SQLHelper.processSql(sql);
        // 有的 sql 语句没有表名，也很短 ， 下面主要考虑 insert into ， replace into ，on duplicate key update 的转换
        // 可能也没有表名，因为是从子查询获取获取的，这时候就能去获取自增键了，否则会报错，现在直接不能让 select 语句通过
        String tableName = SqlUtil.getTableNameFromSql(sql.trim());
        if (!"".equals(tableName)) {
            List<StyleTransfer> styleTransferList = initStyleTransfer();
            Set<String> pKs = dataSourceUtil.getPKs(tableName);
            List<String> autoIncrProperties = dataSourceUtil.getAutoIncrProperties(tableName);
            Map<String, List<String>> pkAndUniqueKeys = dataSourceUtil.getPkAndUniqueKeys(tableName);

            for (StyleTransfer styleTransfer : styleTransferList) {
                if (styleTransfer.isSupport(sql)) {
                    sql = styleTransfer.transfer(sql, tableName, autoIncrProperties,
                            parameterMappingsCopy, parameterMappings, pKs,pkAndUniqueKeys);
                    isModify = true;
                }
            }
        }

        // 对 geo 地理信息函数的支持
        if (PointStyleTransfer.hasGisCalc(sql)) {
            SqlAndParam sqlAndParam = pointStyleTransfer.transfer(sql, parameterMappings, parameterMappingsCopy, parameterObj);
            sql = sqlAndParam.getSql();
            parameterMappings = sqlAndParam.getParameterMappings();
            isModify = true;
        }
        sql = changeDateTimeFormat(sql);
        sql = changeTrue(sql);
        sql = dateFormat(sql);
        if (!isModify) {
            parameterMappings = parameterMappingsCopy;
        }
        // DefaultParameterHandler#setParameters 会遍历 parameterMappings 列表，如果有元素为空，就会报错
        parameterMappings = parameterMappings.stream().filter(Objects::nonNull).collect(Collectors.toList());

        //log.info("当前最终执行的 sql id => {}, sql => {}, params => {}", id, sql, JSONUtil.toJsonStr(parameterMappings));
        BoundSql newBoundSql = newBoundSql(ms, boundSql, sql, parameterMappings);
        if (args.length == 6) {
            args[5] = newBoundSql;
        }
        args[0] = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));

        return invocation.proceed();
    }

    private static BoundSql getBoundSql(Object[] args, SqlSource sqlSource, Object parameterObj) {
        BoundSql boundSql = null;
        //  大部分的 query() 方法都走 4 参数的 query() 方法，但使用 PageHelper 的类走 6 参数的方法
//       参考  https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Interceptor.md#4-%E6%8B%A6%E6%88%AA-query-%E6%96%B9%E6%B3%95%E7%9A%84%E8%A7%84%E8%8C%83
        if (args.length == 6) {
            boundSql = (BoundSql) args[5];
        } else {
            boundSql = sqlSource.getBoundSql(parameterObj);
        }
        return boundSql;
    }

    private static List<StyleTransfer> initStyleTransfer() {
        List<StyleTransfer> transferList = new ArrayList<>();

        transferList.add(new Insert2StyleTransfer());
        transferList.add(new UpdateStyleTransfer());
        transferList.add(new ReplaceIntoStyleTransfer());
        transferList.add(new OnDuplicateKeyUpdateStyleTransfer());


        return transferList;
    }

    private static BoundSql newBoundSql(MappedStatement ms, BoundSql boundSql, String sql, List<ParameterMapping> parameterMappings) {
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql,
                parameterMappings, boundSql.getParameterObject());
        for (ParameterMapping mapping : parameterMappings) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        return newBoundSql;
    }

    private boolean skipPlugin(String id, int idx) {
        String curMapperName = id.substring(0, idx);
        String curMethodName = id.substring(idx + 1);
        return interceptMethodMap.containsKey(curMapperName)&&
                interceptMethodMap.get(curMapperName).contains(curMethodName);
    }

    private boolean isNeedInit() {
        return interceptMethodMap == null || interceptMethodMap.isEmpty();
    }

    private String changeDateTimeFormat(String sql) {
        return sql.replace("%y年%m月%d日", "%y-%m-%d");
    }

    private String changeTrue(String sql) {
        return sql.replaceAll(" true ", " 'TRUE' ")
                .replaceAll(" on\\s+'TRUE' ", " on TRUE ")
                // where 里 then true 是作为判断表达式
                .replaceAll(" then 'TRUE' ", " then true ")
                .replaceAll(" else 'TRUE' ", " else true ")
                .replaceAll(" false ", " 'FALSE' ")
                .replaceAll(" on\\s+'FALSE' ", " on FALSE ")
                .replaceAll(" then 'FALSE' ", " then FALSE ")
                .replaceAll(" else 'FALSE' ", " else FALSE ");
    }

    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /**
     * interceptMethodMap 保存进入插件逻辑的方法
     */
    public void initInterceptMethodMap() {
        List<Object> allMapperBean = dataSourceUtil.getAllMapperBean();
        HashMap<String, ArrayList<String>> map = new HashMap<>();
        for (Object obj : allMapperBean) {
            Class<?> bean = obj.getClass().getInterfaces()[0];
            Method[] declaredMethods = bean.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (AnnotationUtil.hasAnnotation(declaredMethod, SkipTransToDM.class)) {
                    SkipTransToDM annotation = declaredMethod.getAnnotation(SkipTransToDM.class);
                    if (annotation.excludeMethod()) {
                        map.putIfAbsent(bean.getName(), new ArrayList<>());
                        map.get(bean.getName()).add(declaredMethod.getName());
                    }
                } else if (AnnotationUtil.hasAnnotation(bean, SkipTransToDM.class)) {
                    map.putIfAbsent(bean.getName(), new ArrayList<>());
                    map.get(bean.getName()).add(declaredMethod.getName());
                }
            }
        }
        interceptMethodMap = new HashMap<>(map);
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }

    public static String dateFormat(String sql){
        String regex = "[']\\s*%\\s*y";
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(sql);
        while (m.find()){
            int start = m.start();
            int end = m.end();
            sql = new StringBuffer(sql).replace(start, end, "'%Y").toString();
        }
        return sql;
    }

}
//        List<FunctionTransfer> functionTransferList = initFunctionTransfer();
//        List<FormTransfer> formTransferList = initFormTransfer();

//        sql = initTrans(sql);
//
//        for (FormTransfer formTransfer : formTransferList) {
//            if (formTransfer.isSupport(sql)) {
//                sql = cleanSql(sql);
//                sql = formTransfer.transfer(sql);
//            }
//        }
//
//        for (FunctionTransfer functionTransfer : functionTransferList) {
//            if (functionTransfer.isSupport(sql)) {
//                sql = cleanSql(sql);
//                sql = functionTransfer.transfer(sql);
//            }
//        }